package edu.vt.ece.rt.dstm;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import edu.vt.ece.rt.dstm.contention.ContentionManager;
import edu.vt.ece.rt.quorum.ClustersManager;
import edu.vt.ece.rt.quorum.Resource;
import edu.vt.ece.rt.quorum.util.CreateDeleteSet;
import edu.vt.ece.rt.quorum.util.ReadWriteSet;
import edu.vt.ece.rt.util.Logger.Logger;

/**
 * All the STM implementations should implement this interface.
 * Extend this class to create different Implementations
 * TODO: Create a Interface Context 
 * @author	Sudhanshu
 */
public class Context implements Serializable, Comparable<Context>
{
	public static int aborts = 0;
	public static AtomicInteger transactionId = new AtomicInteger(0);
	
	public AtomicInteger status;		//0 = Live, 10 = Aborted, 50 = committed
	protected Long txnId;

	private int lastRetries = 0;
	private int retries = 0;
	
	public ReadWriteSet rwS; // make private later
	private CreateDeleteSet cdS;
	public Context parent;
	public String desc;
	public ArrayList<String> myLockedObjects;
	
	public Object contentionMetadata;

	//To be used by Context delegator
	public Context(){
		rwS = new ReadWriteSet();
		cdS = new CreateDeleteSet();
		status = new AtomicInteger(0);
		myLockedObjects = new ArrayList<String>();
		parent = null;
	}
	
	//Only to create a context on remote node, creator should register the transactionS
	public Context(Long tid){
		rwS = new ReadWriteSet();
		cdS = new CreateDeleteSet();
		status = new AtomicInteger(0);
		txnId = tid;
		retries++;
		myLockedObjects = new ArrayList<String>();
		parent = null;
	}
	
	//TODO: Merge to constructor if atomicBlockId no more required
	public void init(int atomicBlockId) {
		
		status.set(0);			//Set back to live
		
//		txnId = 
//			(ContextDelegator.generator.nextInt(100))*System.currentTimeMillis() * 1000 
//			+
//			ClustersManager.baseClusterId;// + (Thread.currentThread().getId()%100)*10;
		txnId = (long) ((ClustersManager.baseClusterId + 1)*10000000 // MS digit represents node id - 1 
				+
				(ContextDelegator.generator.nextInt(1000)*100)) 
				+ transactionId.incrementAndGet()
				; // remaining digits are random
		register();
	}
	
	public void register(){
		//Register context
		Logger.debug("Registered "+txnId+" with status "+status.get());
		ContextDelegator.contextMap.put(this.txnId, this);
	}
	
	public int getRetries(){
		return lastRetries;
	}
	
	public void complete() {
		lastRetries = retries;
		retries = 0;
//		HyFlow.getConflictManager().complete(this);
	}

	public Long getContextId(){
		return txnId;
	}
	
	@Override
	public String toString() {
		return "[" + txnId + "|" + status + "]";
	}
	
	public void addToReadSet(QObject obj){
		rwS.addReadObject(obj);
	}
	
	public void addToWriteSet(QObject obj){
		rwS.addWriteObject(obj);
	}
	
	public void addToCreateSet(QObject obj){
		cdS.addCreateObject(obj);
	}
	
	public void addToDeleteSet(QObject obj){
		cdS.addDeleteObject(obj);
	}
	
	public Set<QObject> getReadSet(){
		return rwS.getReadObjects();
	}
	
	public Set<QObject> getWriteSet(){
		return rwS.getWriteObjects();
	}
	
	public Set<QObject> getCreateSet(){
		return cdS.getCreateObjects();
	}
	
	public Set<QObject> getDeleteSet(){
		return cdS.getDeleteObjects();
	}
	
	public void setAborted(){
		Logger.debug("Trying to abort "+txnId + " current status "+status.get());
		status.compareAndSet(0, 10);
	}
	
	public void setCommited(){
		Logger.debug("Trying to commit "+txnId + " current status "+status.get());
		status.compareAndSet(0, 50);
	}

	/**
	 * @return
	 */
	public boolean isCommitted() {
		return status.get() == 50;
	} 
	
	public boolean isAborted(){
		return status.get()==10;
	}
	/* (non-Javadoc)
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(Context c) {
		return ContentionManager.resolve(this, c);
	}

	/**
	 * Publishes the Create and Delete set objects locally
	 */
	public void publishLocally() {
		for(QObject obj: getCreateSet()){
			Resource.putQObject(obj.getId(), obj);
			Logger.debug("Publish locally "+obj.getId());
		}
		
		for(QObject obj: getCreateSet()){
			Resource.removeObject(obj);
		}
	}

	/**
	 * @return
	 */
	public int getStatus() {
		return status.get();
	}

	/*
	 * Set the parent for remote context 
	 */
	public void setParent(Long parentID) {
		Context parent = null;
		if (parentID != null) {
			parent = ContextDelegator.getContext(parentID);
		}
		
//		/* Set parent only when it is not set */
//		if (parent == null)
		this.parent = parent;		
	}

	public Long getParentContextId() {
		
		if (parent == null) {
			return null;
		} else {
			return parent.getContextId();
		}
		
	}
	
	public boolean isChild() {
		return parent != null;
	}
	
	public boolean isRoot() {
		return !isChild();
	}

	public void mergeParent() {
		
		for (QObject obj : getReadSet()) { 
			parent.addToReadSet(obj);
			Logger.debug("Adding "+obj.getId()+ " to readSet of parent");
		}
		
		for (QObject obj : getWriteSet()) {
			parent.addToWriteSet(obj);
			Logger.debug("Adding "+obj.getId()+ " to writeSet of parent");
		}
		
	}

	public QObject checkParentReadSet(String id) {
		
		
		for (QObject obj: getReadSet()) {
			if (obj.getId().equals(id)) {
				return obj;
			}
		}
		
		if (parent != null) {
			return parent.checkParentReadSet(id);
		} else {
			return null;
		}
	}

	public QObject checkParentWriteSet(String id) {
		
		
		for (QObject obj: getWriteSet()) {
			if (obj.getId().equals(id)) {
				return obj;
			}
		}
		
		if (parent != null) {
			return parent.checkParentWriteSet(id);
		} else {
			return null;
		}
	}

	public QObject checkWriteSet(String id) {
		
		
		for (QObject obj: getWriteSet()) {
			if (obj.getId().equals(id)) {
				return obj;
			}
		}
		
			return null;
	}

	public QObject checkReadSet(String id) {
		
		
		for (QObject obj: getReadSet()) {
			if (obj.getId().equals(id)) {
				return obj;
			}
		}
		
			return null;
	}

	public QObject checkParentSet(String id) {
		QObject obj;
		if ((obj = checkParentWriteSet(id)) != null) {
			return obj;
		} else {
			return checkParentReadSet(id);
		}
	}

	public void setParent(Context parentContext) {
		this.parent = parentContext;			
		Logger.debug("Setting parent as "+ getParentContextId());	
	}

	public void unsetParent(Context parentContext) {
		
//		ContextDelegator.setCurrentContext(parentContext);		
		if (parentContext != null)
			Logger.debug("Setting current execution context back to "+ parentContext.getContextId());

	}

	
	public static boolean isParent(Context child, Context parent) {
		
		
		while (true) {
			
			if (child.getContextId().intValue() == parent.getContextId().intValue()) {
				return true;
			}
			
			if (child.isRoot()) {
				break;
			} else {
				child = ContextDelegator.getContext(child.getParentContextId());
			}
		}
		
		return false;
	}
	
	public void sanityNested(){
		
		Logger.debug(" Id : " + getContextId() + " description : "+desc);
//		Logger.debug(" Id : " + getContextId() + " description : "+desc);
		if (parent != null) {
			parent.sanityNested();
		}
	}

	public void setDescription(String string) {
		
		desc = string;
		
	}
}
